/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *  SRVNSOperation.m - iPhoneSRV console for Surveyor SRV-1 and SVS 
 *
 *  Created by Nick Kitchener.  Copyright (C) 2009  Surveyor Corporation 
 *            and Nick Kitchener.  All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details (www.gnu.org/licenses)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#import "SRVNSOperation.h"

@implementation SRVNSOperation
@synthesize blackfinController, callback, inputStreamOpened, outputStreamOpened, inputStreamError, outputStreamError, complete, expired;

-(id)init {
	if( self=[super init] ) {
		ready=YES;
		executing=NO;
		finished=NO;
		inputStreamOpened=NO;
		outputStreamOpened=NO;
		inputStreamError=NO;
		outputStreamError=NO;
		expiryTime = 0;
		expiryTimer = nil;
		complete = NO;
		expired = NO;
	}
	return self;
}

// on dealloc, cancel timers, teardown stream client so we don't have any callbacks.
-(void)dealloc {
	if(expiryTimer!=nil) {
		[expiryTimer invalidate];
		expiryTimer = nil;
	}
	[self teardownConnectionClient];
	blackfinController = nil;
	callback = nil;
	[super dealloc];
}

-(void)invokeCallback {
	if(callback!=nil)
		[((NSObject*)callback) performSelectorOnMainThread:@selector(operationCompleted:) withObject:self waitUntilDone:NO];
}

-(void)invokeControllerCallback {
	// note the operation threads do the call back - not using messaging.
	if(blackfinController!=nil)
		[blackfinController controllerOperationCompleted:self];
}

-(BOOL)isConcurrent {
	return YES;
}

-(BOOL)isReady {
	return ready;
}

-(BOOL)isExecuting {
	return executing;
}

-(BOOL)isFinished {
	return finished;
}

-(void)setupConnectionClient {
	CFRunLoopRef cfRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
		
	CFStreamClientContext inputContext = {0, self, NULL, NULL, NULL};
	if( CFReadStreamSetClient([blackfinController inputStream], kCFStreamEventOpenCompleted|kCFStreamEventHasBytesAvailable|kCFStreamEventErrorOccurred|kCFStreamEventEndEncountered, &inputStreamCallBack, &inputContext)) {
		CFReadStreamScheduleWithRunLoop([blackfinController inputStream], cfRunLoop, kCFRunLoopCommonModes);
	}
	
	CFStreamClientContext outputContext = {0, self, NULL, NULL, NULL};
	if( CFWriteStreamSetClient([blackfinController outputStream], kCFStreamEventOpenCompleted|kCFStreamEventCanAcceptBytes|kCFStreamEventErrorOccurred|kCFStreamEventEndEncountered, &outputStreamCallBack, &outputContext)) {
		CFWriteStreamScheduleWithRunLoop([blackfinController outputStream], cfRunLoop, kCFRunLoopCommonModes);
	}

}

-(void)teardownConnectionClient {
	CFRunLoopRef cfRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
	if( [blackfinController inputStream]!=nil )
		CFReadStreamUnscheduleFromRunLoop([blackfinController inputStream], cfRunLoop, kCFRunLoopCommonModes);
	if( [blackfinController outputStream]!=nil )
		CFWriteStreamUnscheduleFromRunLoop([blackfinController outputStream], cfRunLoop, kCFRunLoopCommonModes);
}

static void inputStreamCallBack(CFReadStreamRef stream, CFStreamEventType event, void *myPtr) {
	switch(event) {
		case kCFStreamEventOpenCompleted:
			[((SRVNSOperation*)myPtr) setInputStreamOpened:YES];
			break;
			
		case kCFStreamEventHasBytesAvailable:
			[((SRVNSOperation*)myPtr) haveBytes:stream];
			break;
			
		case kCFStreamEventErrorOccurred:
			[((SRVNSOperation*)myPtr) setInputStreamError:YES];
			CFErrorRef errRef = CFReadStreamCopyError(stream);
			CFStringRef errStr = CFErrorCopyFailureReason(errRef);
			NSLog(@" %@: input stream callback called, error: %@", myPtr, ((NSString*)errStr));
			[((SRVNSOperation*)myPtr) haveError:errRef];
			CFRelease(errStr);
			CFRelease(errRef);
			break;
			
		case kCFStreamEventEndEncountered:
			NSLog(@" %@: input stream callback called - end occured", myPtr);
			[((SRVNSOperation*)myPtr) haveClose];
			break;
			
		default:
			NSLog(@"Unknown input stream callback event occured: %i", event);
			break;
	}
}

static void outputStreamCallBack(CFWriteStreamRef stream, CFStreamEventType event, void *myPtr) {
	switch(event) {
		case kCFStreamEventOpenCompleted:
			[((SRVNSOperation*)myPtr) setOutputStreamOpened:YES];
			break;
			
		case kCFStreamEventCanAcceptBytes:
			[((SRVNSOperation*)myPtr)haveSpace:stream];
			break;
			
		case kCFStreamEventErrorOccurred:
			[((SRVNSOperation*)myPtr) setOutputStreamError:YES];
			CFErrorRef errRef = CFWriteStreamCopyError(stream);
			CFStringRef errStr = CFErrorCopyFailureReason(errRef);
			NSLog(@" %@: write stream callback called, error: %@", myPtr, ((NSString*)errStr));
			[((SRVNSOperation*)myPtr) haveError:errRef];
			CFRelease(errStr);
			CFRelease(errRef);
			break;
			
		case kCFStreamEventEndEncountered:
			NSLog(@" %@: write stream callback called - end occured", myPtr);
			[((SRVNSOperation*)myPtr) haveClose];			
			break;
		default:
			NSLog(@"Unknown output stream callback event occured: %i", event);
			break;
	}
}

-(void)haveBytes:(CFReadStreamRef)inputStream {
	NSLog(@" %@: operation haveBytes: called", self);
}

-(void)haveSpace:(CFWriteStreamRef)outputStream {
	NSLog(@" %@: operation haveSpace: called", self);
}

-(void)haveError:(CFErrorRef)errRef {
	NSLog(@" %@: operation haveError: called", self);
}

-(void)haveClose {
	NSLog(@" %@: operation haveClose: called", self);
}

-(void)start {
	if( [self isCancelled] ) {		
		[self willChangeValueForKey:@"isFinished"];
		finished = YES;
		[self invokeControllerCallback];
		[self invokeCallback];
		[self didChangeValueForKey:@"isFinished"];
	} else {
		[self willChangeValueForKey:@"isExecuting"];
		executing = YES;
		[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
		[self didChangeValueForKey:@"isExecuting"];
	}
}

-(void)main {
	@try {
		
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
		
		NSLog(@" %@: beginning execution", self);

		[self setupConnectionClient];
		do{
			CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, true);
		} while((!complete) && (![self isCancelled]));
		NSLog(@" %@ ending, complete=%i, isCancelled=%i", self, complete, [self isCancelled]);
		if( expiryTimer!=nil ) {
			[expiryTimer invalidate];
			expiryTimer=nil;
		}
		[self teardownConnectionClient];
		[self invokeControllerCallback];
		[self invokeCallback];
		[pool release];
		
		[self willChangeValueForKey:@"isFinished"];
		[self willChangeValueForKey:@"isExecuting"];
		executing = NO;
		finished = YES;
		[self didChangeValueForKey:@"isExecuting"];
		[self didChangeValueForKey:@"isFinished"];
	}
	@catch (NSException * e) {
		NSLog(@" %@: operation thread exception caught %@",self, e);
	}
	@finally {
		
	}
}

-(void)operationExpired:(NSTimer*)theTimer {
	expiryTimer=nil;
	expired = YES;
	complete = YES;
	NSLog(@" %@: operation expired", self);
}

-(void)setOperationTimeout:(CFTimeInterval)seconds {
	expiryTime = seconds;
	if(expiryTime>0) {
		NSLog(@" %@: expiry timer configured %.2f seconds from now", self, expiryTime);
		expiryTimer = [NSTimer scheduledTimerWithTimeInterval:expiryTime
													   target:self
													 selector:@selector(operationExpired:)
													 userInfo:nil
													  repeats:NO];
		
	}
}


@end
